/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * system & local includes:
 **/
#include "commonheaders.h"
#include "svcExImport.h"
#include "classExImContactBase.h"

/**
 * Miranda includes
 **/
#include "m_protocols.h"
#include "m_protosvc.h"

namespace NExImport {
namespace NIniFile {

/***********************************************************************************************************
 * exporting stuff
 ***********************************************************************************************************/

/**
 * name:	ExportModule
 * desc:	write all settings from a database module to file
 * param:	hContact	- handle of contact the module is owned from
 *			pszModule	- name of the module to save
 *			file		- file to write the settings to
 * return	nothing
 **/
static VOID ExportModule(HANDLE hContact, LPCSTR pszModule, FILE* file)
{
	ENUMLIST settingList;
	LPENUMLISTITEM pSetting;
	DBVARIANT dbv;
	LPSTR here;
	WORD j;

	if(!DBEnumSettings(hContact, pszModule, &settingList)) return;

	// print the module header..
	fprintf(file, "[%s]\n", pszModule);
	for(pSetting = settingList.first; pSetting != NULL; pSetting = pSetting->next) {
		if(DBGetSettingUtf8(hContact, pszModule, pSetting->pszName, &dbv))
			continue;
		
		switch(dbv.type) {
			case DBVT_BYTE:
				fprintf(file, "%s=b%u\n", pSetting->pszName, dbv.bVal);
			break;
			case DBVT_WORD:
				fprintf(file, "%s=w%u\n", pSetting->pszName, dbv.wVal);
			break;
			case DBVT_DWORD:
				fprintf(file, "%s=d%u\n", pSetting->pszName, dbv.dVal);
			break;
			case DBVT_UTF8:
			case DBVT_ASCIIZ:
				for(here = dbv.pszVal; here && *here; here++) {
					switch(*here) {
						// convert \r to STX 
						case '\r':
							*here = 2;
							break;
						// convert \n to ETX
						case '\n':
							*here = 3;
							break;
					}
				}
				if(dbv.type == DBVT_UTF8) fprintf(file, "%s=u%s\n", pSetting->pszName,dbv.pszVal);
				else fprintf(file, "%s=s%s\n", pSetting->pszName, dbv.pszVal);
				break;
			case DBVT_BLOB:
				fprintf(file, "%s=B", pSetting->pszName);
				for(j = 0; j < dbv.cpbVal; j++)
					fprintf(file, "%02X ", (BYTE)dbv.pbVal[j]);
				fputc('\n', file);
				break;
		}
		DBFreeVariant(&dbv);
	}
	fprintf(file, "\n");
	DBFreeEnumList(&settingList);
}

/**
 * name:	ExportContact
 * desc:	Exports a certain contact to an ini file.
 * param:	hContact	- contact to export or -1 to export all contacts
 *			pModules	- module to export, NULL to export all modules of a contact
 *			file		- ini file to write the contact to
 **/
static BOOLEAN ExportContact(HANDLE hContact, LPENUMLIST pModules, FILE* file)
{
	LPENUMLISTITEM mod;
	CExImContactBase vcc;

	if((vcc = hContact) == NULL) 
		return FALSE;
	vcc.toIni(file);

	for(mod = pModules->first; mod != NULL; mod = mod->next) {
		if(mir_stricmp(mod->pszName, "Protocol"))
			ExportModule(hContact, mod->pszName, file);
	}
	return TRUE;
}

/**
 * name:	Export
 * desc:	Exports a certain contact or all contacts to an ini file.
 * param:	hContact	- contact to export or -1 to export all contacts
 *			pszFileName	- ini-filename to write the contact to
 **/
INT Export(HANDLE hContact, LPCSTR pszFileName) // hContact == -1 export entire db. module == NULL export entire contact.
{
	FILE* file;
	ENUMLIST Modules;

	if(!SelectModulesToExport(hContact, &Modules, NULL)) {

		if((file = fopen(pszFileName, "wt")) == NULL) {
			DBFreeEnumList(&Modules);
			MsgErr(NULL, 
				_T("The ini-file \"%s\"\nfor saving contact information could not be opened."),
				pszFileName);
			return 1;
		}
			SetCursor(LoadCursor(NULL, IDC_WAIT));

		if(Modules.first == NULL)
			DBEnumModules(&Modules);

		if(hContact == INVALID_HANDLE_VALUE) {
			for(hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
				hContact && hContact != INVALID_HANDLE_VALUE;
				hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
			{
				ExportContact(hContact, &Modules, file);
			}
		}
		else {
			ExportContact(hContact, &Modules, file);
		}
		DBFreeEnumList(&Modules);
		fclose(file);
		SetCursor(LoadCursor(NULL, IDC_ARROW));
	}
	return 0;
}

/***********************************************************************************************************
 * importing stuff
 ***********************************************************************************************************/

LPSTR strnrchr(LPSTR string, INT ch, DWORD len)
{
    LPSTR start = (LPSTR)string;

    string += len;                       /* find end of string */
                                            /* search towards front */
    while (--string != start && *string != (CHAR)ch);
    if(*string == (CHAR)ch)                /* char found ? */
            return ((LPSTR)string);
    return(NULL);
}

/**
 * name:	ImportreadLine
 * desc:	read exactly one line into a buffer and return its pointer. Size of buffer is managed.
 * param:	file	- pointer to a file
 *			string	- the string to write the read line to
 * return:	pointer to the buffer on success or NULL on error
 **/
static DWORD ImportreadLine(FILE* file, string& str)
{
	CHAR c;
	DWORD l = 0;
	BOOLEAN bComment = 0;

	str.clear();
	while(!feof(file)) {
		switch(c = fgetc(file)) {
			case EOF:
				// reading error
				if(ferror(file)) {
					str.clear();
					return 0;
				}
				// end of line & file
				return l;
			
			case '\r':
			case '\n':
				// ignore empty lines
				if(l == 0) { 
					bComment = 0;
					continue;
				}
				return l;
			
			case ';':
				// found a comment line
				bComment |= l == 0;
			case '\t':
			case ' ':
				if(l == 0) break;		// ignore space and tab at the beginning of the line
			
			default:
				if(!bComment) {
					str.push_back(c);
					l++;
				}
				break;
		}
	}
	return 0;
}

/**
 * name:	ImportFindContact
 * desc:	This function decodes the given line, which is already identified to be a contact line.
 *			The resulting information is matcht to the given hContact if it isn't NULL.
 *			Otherwise all existing contacts are matched.
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			pszBuf		- pointer to the buffer holding the string of the current line in the ini.-file
 *			cchBuf		- character count of the buffer
 * return:	handle to the contact that matches the information or NULL if no match
 **/


static HANDLE ImportFindContact(HANDLE hContact, string& strBuf, BOOLEAN bCanCreate)
{
	CExImContactBase vcc;

	vcc.fromIni(strBuf);
	if(hContact != INVALID_HANDLE_VALUE) {
		if(vcc.isHandle(hContact))
			return hContact;
	}
	else if(bCanCreate)	
		return vcc.toDB();
	
	return vcc.handle();
}

/**
 * name:	ImportSetting
 * desc:	This function writes a line identified as a setting to the database
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			pszModule	- module to write the setting to
 *			strLine		- string with the setting and its value to write to db
 * return:	0 if writing was ok, 1 otherwise
 **/
INT ImportSetting(HANDLE hContact, LPCSTR pszModule, string& strLine)
{
	DBCONTACTWRITESETTING cws;
	LPSTR end, value;
	DWORD numLines = 0;
	DWORD brk;
	LPSTR pszLine = (LPSTR)&strLine[0];

	if(!pszModule || !*pszModule) return 1;
	if((end = value = mir_strchr(pszLine, '=')) == NULL) return 1;

	// truncate setting string if it has spaces at the end
	do { 
		if(end == pszLine)
			return 1;
		*(end--) = 0; 
	} while(*end == '\t' || *end == ' ' || *end < 27);

	cws.szModule = pszModule;
	cws.szSetting = pszLine;

	// skip spaces from the beginning of the value
	do { 
		value++; 
		// if the value is empty, delete it from db
		if(*value == '\0')
			return DBDeleteContactSetting(hContact, pszModule, pszLine);
	} while(*value == '\t' || *value == ' ');

	// decode database type and value
	switch(*(value++)) {
		case 'b':
			if(brk = strspn(value, "0123456789-"))
				*(value + brk) = 0;
			cws.value.type = DBVT_BYTE;
			cws.value.bVal = (BYTE)atoi(value);
			break;
		case 'w':
			if(brk = strspn(value, "0123456789-"))
				*(value + brk) = 0;
			cws.value.type = DBVT_WORD;
			cws.value.wVal = (WORD)atoi(value);
			break;
		case 'd':
			if(brk = strspn(value, "0123456789-"))
				*(value + brk) = 0;
			cws.value.type = DBVT_DWORD;
			cws.value.dVal = (DWORD)atoi(value);
			break;
		case 's':
		case 'u':
			cws.value.type = *(value - 1) == 's' ? DBVT_ASCIIZ : DBVT_UTF8;
			cws.value.pszVal = value;
			
			for(end = value; end && *end; end++) {
				switch(*end) {
					// convert STX back to \r
					case 2:
						*end = '\r';
						break;
					// convert ETX back to \n
					case 3:
						*end = '\n';
						break;
				}
			}
			break;
		
		case 'B':
		{
			PBYTE dest;

			cws.value.type = DBVT_BLOB;
			cws.value.cpbVal = (WORD)mir_strlen(value) / 3;
			cws.value.pbVal = (PBYTE)value;
			for(dest = cws.value.pbVal, value = strtok(value, " "); value && *value; value = strtok(NULL, " "))
				*(dest++) = (BYTE)strtol(value, NULL, 16);
			*dest = 0;
			break;
		}
		case 'U':
			cws.value.type = DBVT_UTF8;
			cws.value.pszVal = value;
			for(end = cws.value.pszVal, value = strtok(value, " "); value && *value; value = strtok(NULL, " "))
				*(end++) = (BYTE)strtol(value, NULL, 16);
			*end = 0;
			break;
		
		default:
			cws.value.type = DBVT_DELETED;
			return 1;
	}
	return CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&cws);
}

/**
 * name:	Import
 * desc:	This function imports an ini file
 * param:	hContact	- handle to contact to match or NULL to match all existing
 *			file		- module to write the setting to
 *			strLine		- string with the setting and its value to write to db
 * return:	0 if writing was ok, 1 otherwise
 **/
INT Import(HANDLE hContact, LPCSTR pszFileName)
{
	FILE* file;
	HANDLE hNewContact = INVALID_HANDLE_VALUE;
	DWORD end, numLines = 0;
	CHAR szModule[MAXSETTING];
	WORD numContactsInFile = 0,		// number of contacts in the inifile
		 numContactsAdded = 0;		// number of contacts, that were added to the database
	string strBuf;
	
	if(file = fopen(pszFileName, "rt")) {
		SetCursor(LoadCursor(NULL, IDC_WAIT));

		while(ImportreadLine(file, strBuf)) {
			numLines++;

			// contact was found and imported
			if(hContact != INVALID_HANDLE_VALUE && hNewContact != INVALID_HANDLE_VALUE)
				break;

			if(hContact) {
				// there are some modules of a contact
				if(!strncmp(strBuf.c_str(), "FROM CONTACT:", 13)) {
					strBuf.erase(0, 13);
					while(strBuf[0] == ' ' || strBuf[0] == '\t')
						strBuf.erase(0,1);

					*szModule = 0;
					numContactsInFile++;
					if((hNewContact = ImportFindContact(hContact, strBuf, FALSE)) != INVALID_HANDLE_VALUE)
						numContactsAdded++;
					continue;
				}
			}
			else {
				// importing settings is only valid vor the main menu item
				if(!strncmp(strBuf.c_str(), "SETTINGS:", 9)) {
					*szModule = 0;
					hNewContact = NULL;
					continue;
				}
			}

			// there is a contact to add
			if(!strncmp(strBuf.c_str(), "CONTACT:", 8)) {
				strBuf.erase(0, 8);
				while(strBuf[0] == ' ' || strBuf[0] == '\t')
					strBuf.erase(0,1);

				*szModule = 0;
				numContactsInFile++;			
				if((hNewContact = ImportFindContact(hContact, strBuf, TRUE)) != INVALID_HANDLE_VALUE)
					numContactsAdded++;
				continue;
			}

			// read modules and settings only for valid contacts
			if(hNewContact != INVALID_HANDLE_VALUE) {
				// found a module line
				if(strBuf[0] == '[' && (end = strBuf.find_first_of(']')) > 0 && end != string::npos) {
					mir_strncpy(szModule, &strBuf[1], end);
					continue;
				}
				// try to import a setting
				ImportSetting(hNewContact, szModule, strBuf);
			}
		}
		fclose(file);
		SetCursor(LoadCursor(NULL, IDC_ARROW));

		if(hContact) {
			// the contact was not found in the file
			if(numContactsInFile > 0 && !numContactsAdded) {
				MsgErr(NULL,
					_T("None of the %d contacts, stored in the ini-file, match the selected contact!\nNothing will be imported"),
					numContactsInFile);
			}
		}
		else {
			MsgBox(NULL, MB_ICON_INFO, _T("Import complete"), _T("Some basic statistics"),
				_T("Added %d of %d contacts stored in the ini-file."),
				numContactsAdded, numContactsInFile);
		}
		return 0;
	}
	MsgErr(NULL, 
		_T("The ini-file \"%s\"\nfor reading contact information could not be opened."),
		pszFileName);
	return 1;
}

} // namespace NIniFile
} // namespace NExImport
